package com.example.demo.parser;

import com.example.demo.abs.AbstractCodeGenParser;
import com.example.demo.template.MybatisTemplate;
import com.example.demo.util.FileUtil;
import com.example.demo.util.db.ColumnDef;
import com.example.demo.util.db.DatabaseUtil;
import com.example.demo.util.db.TableDef;
import org.apache.commons.lang3.StringUtils;
import org.apache.poi.openxml4j.exceptions.InvalidFormatException;

import java.io.FileInputStream;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;

/**
 * Mybatis代码生成解析器
 */
public class MybatisCodeGenParser extends AbstractCodeGenParser {

    /**
     * PDM类型
     */
    private static final String SOURCE_TYPE_PDM = "1";
    /**
     * Excel类型
     */
    private static final String SOURCE_TYPE_EXCEL = "2";
    /**
     * 字段名称是否包含数据库类型
     */
    private static final boolean FIELD_NAME_INCLUDE_TYPE = true;
    /**
     * 基础字段集合
     */
    private static List<String> baseFields = Arrays.asList(new String[]{"id", "createDate", "modDate"});
    /**
     * 数据库表定义
     */
    private TableDef table;
    /**
     * 实体类名称
     */
    private String entityName = "";
    /**
     * 实体对象名称
     */
    private String entityVarName = "";


    /**
     * 作者名
     * @return
     */
    public String author() {
        String userName = "老牛";
        return userName;
    }

    /**
     * 时间
     * @return
     */
    public String time() {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        return sdf.format(new Date());
    }

    /**
     * 字段列表
     * @return
     */
    public String fields() {
        StringBuilder fieldStringBuilder = new StringBuilder();

        for (ColumnDef column : table.getColumns()) {
            String javaType = getJavaTypeFromDbType(column.getDataType(), column.getLength(), column.getPrecision());
            boolean booleanType = "boolean".equalsIgnoreCase(javaType);
            String javaField = getJavaFieldNameFromDbName(column.getCode(), booleanType);

            if (baseFields.contains(javaField)) {
                continue;
            }

            fieldStringBuilder.append("\t/**" + "\n");
            fieldStringBuilder.append("\t * " + column.getName() + "\n");

            if (column.getComment() != null && !"".equals(column.getComment()) && !column.getName().equals(column.getComment())) {
                fieldStringBuilder.append("\t * <pre>" + "\n");
                fieldStringBuilder.append("\t * " + column.getComment().replaceAll("(?i)(\\s{2})", "\n\t * ") + "\n");
                fieldStringBuilder.append("\t * </pre>" + "\n");
            }
            fieldStringBuilder.append("\t*/\n");
            fieldStringBuilder.append("\tprivate " + javaType + " " + javaField + ";" + "\n");
        }
        return fieldStringBuilder.toString();
    }

    /**
     * getterAndSetter列表
     * @return
     */
    public String getterAndSetter() {
        StringBuilder getterAndSetterStringBuilder = new StringBuilder();

        for (ColumnDef column : table.getColumns()) {
            String javaType = getJavaTypeFromDbType(column.getDataType(), column.getLength(), column.getPrecision());
            boolean booleanType = "boolean".equalsIgnoreCase(javaType);
            String javaField = getJavaFieldNameFromDbName(column.getCode(), booleanType);
            String javaFieldU = getFieldGetterSetterName(column.getCode(), booleanType);

            if (baseFields.contains(javaField)) {
                continue;
            }

            getterAndSetterStringBuilder.append("\tpublic " + javaType + (booleanType ? " is" : " get") + javaFieldU + "() {" + "\n");
            getterAndSetterStringBuilder.append("\t\treturn " + javaField + ";" + "\n");
            getterAndSetterStringBuilder.append("\t}" + "\n");
            getterAndSetterStringBuilder.append("\n");
            getterAndSetterStringBuilder.append("\tpublic void set" + javaFieldU + "(" + javaType + " " + javaField + ") {" + "\n");
            getterAndSetterStringBuilder.append("\t\tthis." + javaField + " = " + javaField + ";" + "\n");
            getterAndSetterStringBuilder.append("\t}" + "\n");
            getterAndSetterStringBuilder.append("\n");
        }

        return getterAndSetterStringBuilder.toString();
    }

    /**
     * 字段枚举
     * @return
     */
    public String columnEnum() {
        StringBuilder columnEnumBuilder = new StringBuilder();

        columnEnumBuilder.append("\t/**" + "\n");
        columnEnumBuilder.append("\t * 字段枚举\n");
        columnEnumBuilder.append("\t */" + "\n");
        columnEnumBuilder.append("\tpublic enum Column {" + "\n");
        for (int i = 0; i < table.getColumns().size(); i++) {
            ColumnDef columnDef = table.getColumns().get(i);
            String endStr = ",";
            if (i == table.getColumns().size() - 1) {
                endStr = ";";
            }
            columnEnumBuilder.append("\t\t/** " + columnDef.getName() + " */\n");
            columnEnumBuilder.append("\t\t" + columnDef.getCode().toUpperCase() + endStr + "\n");
        }
        columnEnumBuilder.append("\t}" + "\n");

        return columnEnumBuilder.toString();
    }

    /**
     * SQL语句select部分
     * @return
     */
    public String selectSQL() {
        List<String> columnNameList = new ArrayList<String>();
        for (ColumnDef column : table.getColumns()) {
            columnNameList.add("t." + column.getCode());
        }

        return StringUtils.join(columnNameList, ", ");
    }

    /**
     * SQL语句where部分
     * @return
     */
    public String whereSQL() {
        StringBuilder sb = new StringBuilder();
        for (ColumnDef column : table.getColumns()) {
            String javaType = getJavaTypeFromDbType(column.getDataType(), column.getLength(), column.getPrecision());
            boolean booleanType = "boolean".equalsIgnoreCase(javaType);
            String javaField = getJavaFieldNameFromDbName(column.getCode(), booleanType);

            if ("String".equals(javaType)) {
                sb.append("\t\t<if test=\"" + javaField + " != null and " + javaField + " != ''\">\n");
            } else {
                sb.append("\t\t<if test=\"" + javaField + " != null\">\n");
            }
            sb.append("\t\tAND t." + column.getCode() + " = #{" + javaField + "}\n");
            sb.append("\t\t</if>\n");
        }
        return sb.deleteCharAt(sb.length() - 1).toString();
    }

    @Override
    public String _fileName() {
        return entityName;
    }

    public String entityName() {
        return entityName;
    }

    public String packageName() {
        return "com.example.demo";
    }

    public String entityVarName() {
        return entityVarName;
    }

    public String tableName() {
        return table.getCode().toUpperCase();
    }

    public String tableDesc() {
        String name = table.getName();
        if (name.endsWith("表")) {
            name = name.substring(0, name.length() - 1);
        }
        return name;
    }

    @Override
    public void setParams(String[] params) {
        super.setParams(params);

        String sourceType = params[0];
        try {
            if (sourceType.equals(SOURCE_TYPE_PDM)) {
                // pdm文件
                initPdmParam(params);
            } else if (sourceType.equals(SOURCE_TYPE_EXCEL)) {
                // excel文件
                initExcelParam(params);
            } else {
                throw new RuntimeException("无法识别的数据源类型【" + sourceType + "】");
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 初始化pdm数据参数
     * @param params
     * @throws IOException
     */
    private void initPdmParam(String[] params) throws IOException {
        String filePath;
        if (params.length > 2) {
            filePath = params[2];
        } else {
            filePath = "pdm/" + params[1] + ".pdm";
        }

        table = DatabaseUtil.parseTableDefFromPdm(new FileInputStream(FileUtil.parseFile(filePath)), params[1]);
        System.out.println("正在读取:" + table.getName() + "【" + table.getCode() + "】，" + table.getColumns().size() + "个字段。");

        if (table == null) {
            throw new RuntimeException("找不到表【" + params[1] + "】的定义");
        }
        entityName = getJavaClassNameFromDbName(table.getCode());
        entityVarName = Character.toLowerCase(entityName.charAt(0)) + entityName.substring(1);
    }

    /**
     * 初始化excel数据参数
     * @param params
     * @throws InvalidFormatException
     * @throws IOException
     */
    private void initExcelParam(String[] params) throws InvalidFormatException,
            IOException {
        String filePath;
        if (params.length > 2) {
            filePath = params[2];
        } else {
            filePath = "excel/" + params[1] + ".xls";
        }

        table = DatabaseUtil.parseTableDefFromExcel(new FileInputStream(FileUtil.parseFile(filePath)), params[1]);

        System.out.println("正在读取:" + table.getName() + "【" + table.getCode() + "】，" + table.getColumns().size() + "个字段。");

        entityName = getJavaClassNameFromDbName(table.getCode());
        entityVarName = Character.toLowerCase(entityName.charAt(0)) + entityName.substring(1);
    }

    /**
     * 根据数据库名称获取java类名称（首字母大写）
     * @param dbName
     * @return
     */
    private String getJavaClassNameFromDbName(String dbName) {
        String result = "";
        String[] javaNameSplit = getJavaNameSplit(dbName, true);

        for (int i = 2; i < javaNameSplit.length; i++) {
            String word = javaNameSplit[i].toLowerCase();
            result += Character.toUpperCase(word.charAt(0)) + word.substring(1);
        }

        return result;
    }


    /**
     * 根据数据库名称获取java字段名称（首字母小写）
     *
     * @param dbName
     * @return
     */
    private String getJavaFieldNameFromDbName(String dbName, boolean booleanType) {
        String className = "";
        String[] javaNameSplit = getJavaNameSplit(dbName, FIELD_NAME_INCLUDE_TYPE);

        for (int i = 0; i < javaNameSplit.length; i++) {
            String word = javaNameSplit[i].toLowerCase();

            // 对于数据库boolean类型的字段，java变量不要有开头的is
            if (booleanType && i == 0 && "is".equals(word)) {
                continue;
            }
            className += Character.toUpperCase(word.charAt(0)) + word.substring(1);
        }

        return Character.toLowerCase(className.charAt(0)) + className.substring(1);
    }

    /**
     * 获取getter和setter后面部分名称的字符串
     * @param dbName
     * @return
     */
    private String getFieldGetterSetterName(String dbName, boolean booleanType) {
        String fieldName = getJavaFieldNameFromDbName(dbName, booleanType);

        if (fieldName.length() > 1 && Character.isLowerCase(fieldName.charAt(1))) {
            fieldName = Character.toUpperCase(fieldName.charAt(0)) + fieldName.substring(1);
        }
        return fieldName;
    }

    /**
     * 获取要拼接java名称的单词数组
     * @param dbName
     * @return
     */
    private String[] getJavaNameSplit(String dbName, boolean includeType) {
        List<String> result = new ArrayList<String>();
        String[] split = dbName.split("_");
        for (int i = 0; i < split.length; i++) {
            if (!includeType && i == 0 && split[i].length() <= 1) {
                continue;
            }

            result.add(split[i]);
        }

        return result.toArray(new String[0]);
    }

    /**
     * 根据数据库类型获取java类型
     * @param dbType
     * @return
     */
    private String getJavaTypeFromDbType(String dbType, Integer length, Integer precision) {
        // (*.pdm，没定义字段的数据类型，则默认返回字符串)
        if (null == dbType) {
            return "String";
        }
        dbType = dbType.toLowerCase();
        if ("boolean".equals(dbType)) {
            return "Boolean";
        }
        if ("bigint".equals(dbType)) {
            return "Long";
        }
        if ("double".equals(dbType) || dbType.startsWith("decimal")) {
            return "Double";
        }
        if ("int".equals(dbType) || "smallint".equals(dbType) || "tinyint".equals(dbType)) {
            return "Integer";
        }
        if ("date".equals(dbType) || "datetime".equals(dbType) || "timestamp".equals(dbType)) {
            return "Date";
        }
        if (dbType.startsWith("char") || dbType.startsWith("varchar") || "text".equals(dbType)) {
            return "String";
        }
        if (dbType.startsWith("numeric")) {
            if (precision == null || precision == 0) {
                // (*.pdm,numeric数据类型，但没定义长度则默认返回)
                if (null == length || length < 10) {
                    return "Integer";
                }
                return "Long";
            } else {
                return "Double";
            }
        }
        throw new RuntimeException("无法识别的数据库字段类型【" + dbType + "】");
    }

    /**
     * 代码生成入口
     * @param args
     * @throws SecurityException
     * @throws IllegalArgumentException
     * @throws IOException
     * @throws ClassNotFoundException
     * @throws InstantiationException
     * @throws IllegalAccessException
     * @throws NoSuchMethodException
     * @throws InvocationTargetException
     */
    public static void main(String[] args) throws SecurityException, IllegalArgumentException, IOException, ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
        MybatisTemplate template = new MybatisTemplate();
        Class<? extends AbstractCodeGenParser> parserClass = MybatisCodeGenParser.class;
        template.setParserClassName(parserClass.getName());
        template.setDesDir("target/" + parserClass.getSimpleName());
        template.setTemplatePath("classpath:" + parserClass.getSimpleName() + "/template");
        template.setParameterFile("classpath:" + parserClass.getSimpleName() + "/param.ini");
        template.generate();

        System.out.println("代码生成成功，路径：" + FileUtil.parseFile(template.getDesDir()).getAbsolutePath());
    }

}
